#include "common.h"
#include "server.h"
#include "client.h"
#include "semaphore.h"

#ifdef DEBUG
#define KEYFILE				"localkeys/merchant.pem"
#else
#define KEYFILE				"realkeys/merchant.pem"
#endif

#define PASSWORD			"password"
#define PI_OK				"PAYMENT ACCEPTED"
#define PI_NOK				"PAYMENT REJECTED"
#define SI_OK				"ORDER SHIPPED"
#define SI_NOK				"SHIPPING ERROR"

#define NSEC_PER_MSEC 1000000

void handle_connection(SSL *customer, int s);
void prep_order_conf(struct OrderInfo* oi, struct ProductEntry* pe);

int trans_seq_id = 0;
std::string log_fname;

int main(int argc, char **argv) {
	log_fname = "logs/transactions.log";
	if (argc > 1) {
		log_fname = log_fname.insert(log_fname.find_last_of('.'), argv[1]);
	}

	SSLServer* server = new SSLServer(KEYFILE, PASSWORD, MERCHANT_SERVER_PORT, handle_connection);
	server->WaitForConnection();
	exit(0);
}

void handle_connection(SSL *customer, int s) {
	SSLClient* creditco;
	SSLClient* carrier;
	FILE* logFile = NULL;
	char* pi_buf;
	char* si_buf;
	char* cert_buf;
	uint32_t pi_enc_pkt_size, pi_net_enc_pkt_size;
	uint32_t si_enc_pkt_size, si_net_enc_pkt_size;
	char resp[3];
	char logBuffer[128];
	struct timespec start_time;
	struct timespec end_time;
	int r;
	int len = 0;

#ifndef NOCERT
	//write credit co cert
	FILE* ccPem = fopen(CREDITCO_PUB_KEY, "r");
	cert_buf = new char[BUFSIZZ];

	memset(cert_buf, 0, BUFSIZZ);

	while ((r = fread(cert_buf, 1, BUFSIZZ - 1, ccPem)) > 0) {
		SSL_write(customer, cert_buf, r);
	}
	resp[0] = '\0';
	SSL_write(customer, resp, 1);

	printf("Sent creditco pem\n");

	//write carrier cert
	FILE* carrierPem = fopen(CARRIER_PUB_KEY, "r");

	while ((r = fread(cert_buf, 1, BUFSIZZ, carrierPem)) > 0) {
		SSL_write(customer, cert_buf, r);
	}

	SSL_write(customer, resp, 1);
	printf("Sent carrier pem\n");

	delete[] cert_buf;
#endif

	//get OI
	struct OrderInfo oi;
	struct ProductEntry pe[4];
	SSL_read_until_full(customer, &oi, sizeof(struct OrderInfo));

	// Receive Message 1: Preliminary Order Information ----
	SSL_read_until_full(customer, pe, sizeof(struct ProductEntry) * oi.totalItemsOrdered);
//	for (unsigned int i = 0; i < oi.totalItemsOrdered; i++) {
//		printf("startByte: %d\n", pe[i].startByte);
//		printf("id:        %hd\n", (unsigned short) *(pe[i].id));
//		printf("name:      %s\n", pe[i].name);
//		printf("unitPrice: %s\n", pe[i].unitPrice);
//		printf("quantity:  %s\n", pe[i].quantity);
//		printf("endByte:   %d\n\n", pe[i].endByte);
//	}

	// Send Message 2: Order Confirmation ----
	prep_order_conf(&oi, pe);
	SSL_write(customer, &oi, sizeof(struct OrderInfo));
	SSL_write(customer, pe, sizeof(struct ProductEntry)* oi.totalItemsOrdered);

	// Receive Message 3: OI Message ----
	memset(&oi, 0, sizeof(struct OrderInfo));
	SSL_read_until_full(customer, &oi, sizeof(struct OrderInfo));

	// get size of PI EVP packet
	SSL_read_until_full(customer, &pi_net_enc_pkt_size,	sizeof(pi_net_enc_pkt_size));
	pi_enc_pkt_size = ntohl(pi_net_enc_pkt_size);
	printf("PI size:%lu\n", (unsigned long) pi_enc_pkt_size);
	pi_buf = new char[pi_enc_pkt_size];
	SSL_read_until_full(customer, pi_buf, pi_enc_pkt_size);

	// get size of SI EVP packet
	SSL_read_until_full(customer, &si_net_enc_pkt_size,	sizeof(si_net_enc_pkt_size));
	si_enc_pkt_size = ntohl(si_net_enc_pkt_size);

	printf("SI size:%lu\n", (unsigned long)si_enc_pkt_size);
	si_buf = new char[si_enc_pkt_size];
	SSL_read_until_full(customer, si_buf, si_enc_pkt_size);
	printf("got it all size:%lu\n", (unsigned long)si_enc_pkt_size);

	// Message 4 Received ----

	/* start time of transaction */
	clock_gettime(CLOCK_MONOTONIC, &start_time);

	/* connect with the credit card company */
	creditco = new SSLClient(KEYFILE, PASSWORD);
	creditco->Connect(CREDITCO_ADDR, CREDITCO_SERVER_PORT);

	/* send PI*/
	creditco->Write(&pi_net_enc_pkt_size, sizeof(pi_net_enc_pkt_size));
	creditco->Write(pi_buf, pi_enc_pkt_size);
	delete[] pi_buf;

	/*wait for positive response */
	creditco->ReadUntilFull(resp, 2);
	delete creditco;

	resp[2] = '\0';
	printf("PI resp: %s\n", resp);
	if (memcmp(resp, ACK_STRING, sizeof(ACK_STRING) - 1) != 0) {
		SSL_write(customer, PI_NOK, sizeof(PI_NOK));
		exit(0);
	}

	/* connect with the shipping carrier */
	carrier = new SSLClient(KEYFILE, PASSWORD);
	carrier->Connect(CARRIER_ADDR, CARRIER_SERVER_PORT);

	/* send SI*/
	carrier->Write(&si_net_enc_pkt_size, sizeof(si_net_enc_pkt_size));
	carrier->Write(si_buf, si_enc_pkt_size);
	delete[] si_buf;

	/* wait for positive response */
	carrier->ReadUntilFull(resp, 2);
	clock_gettime(CLOCK_MONOTONIC, &end_time);
	printf("SI resp: %s\n", resp);
	delete carrier;

	/* log time of transaction */
	while ((logFile = fopen(log_fname.c_str(), "a")) == NULL)
		;

	int log_len = snprintf(logBuffer, sizeof(logBuffer), "%lu,%lu,%lu,%lu\n",
      (unsigned long) start_time.tv_sec, (unsigned long) start_time.tv_nsec/NSEC_PER_MSEC,
      (unsigned long) end_time.tv_sec, (unsigned long) end_time.tv_nsec/NSEC_PER_MSEC);
	fwrite(logBuffer, log_len, 1, logFile);
	fclose(logFile);

	/* Respond to customer */
	SSL_write(customer, PI_OK, sizeof(PI_OK));

	if (memcmp(resp, ACK_STRING, sizeof(ACK_STRING) - 1) == 0) {
		SSL_write(customer, SI_OK, sizeof(SI_OK));
	} else {
		SSL_write(customer, SI_NOK, sizeof(SI_NOK));
	}
}

void prep_order_conf(struct OrderInfo* oi, struct ProductEntry* pe) {
	strncpy(oi->merchant_id, "1123581321", OI_MERCHANT_ID_LEN);
	time_t now = time(NULL);
	tm* time_ptr = gmtime(&now);
	const char* format = "%d%m%y";
	char date[OI_DATE_LEN + 1];
	strftime(date, sizeof(date), format, time_ptr);
	strncpy(oi->date, date, OI_DATE_LEN);
	char seq[OI_TRANS_ID_LEN + 1];
	snprintf(seq, sizeof(seq), "%08d", ++trans_seq_id);
	strncpy(oi->trans_id, seq, OI_TRANS_ID_LEN);

	int quantity = 0;
	double order_cost = 0.0, unit_cost = 0.0;
	char cost_str[OI_PROD_ENTRY_PRICE_LEN + 1], quantity_str[OI_PROD_ENTRY_NUM_LEN];
	for (int i = 0; oi->totalItemsOrdered && i < oi->totalItemsOrdered; i++) {
		strncpy(cost_str, pe[i].unitPrice, OI_PROD_ENTRY_PRICE_LEN);
		unit_cost = atof(cost_str);
		strncpy(quantity_str, pe[i].quantity, OI_PROD_ENTRY_NUM_LEN);
		quantity = atoi(quantity_str);
		order_cost += quantity * unit_cost;
	}
	char total[sizeof(oi->totalCharged) + 1];
	snprintf(total, sizeof(total), "%.2f", order_cost);
	strncpy(oi->totalCharged, total, OI_TOTAL_CHARGED_LEN);
//	printf("order_cost: %.2f\n", order_cost);

//	char merchant_id[11];
//	strncpy(merchant_id, oi->merchant_id, 10);
//	char date2[7];
//	strncpy(date2, oi->date, 6);
//	char tid[9];
//	strncpy(tid, oi->trans_id, 8);
//	printf("\nmerchant_id: %s", merchant_id);
//	printf("\ndate:        %s", date2);
//	printf("\ntrans_id:    %s\n", tid);
}
